/**
 * 
 */
package edu.gatech.oad.mule.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import javax.swing.JOptionPane;

import edu.gatech.oad.mule.Game;
import edu.gatech.oad.mule.config.interfaces._ConfigurationPanel;
import edu.gatech.oad.mule.input.InputEvent;
import edu.gatech.oad.mule.input.interfaces.ControllerOwner;
import edu.gatech.oad.mule.input.interfaces.InputListener;
import edu.gatech.oad.mule.view.interfaces.RenderableGameDriver;
import edu.gatech.oad.mule.view.interfaces.RenderableMap;
import edu.gatech.oad.mule.view.interfaces.RenderablePlayer;
import edu.gatech.oad.mule.view.interfaces.RenderableTile;

/**
 * Regulates the flow of a single game of mule through transitions between phase
 * object which handle the unique game logic for their phase.
 * 
 * @author Dale Avery
 * 
 */
public final class NewGameDriver extends GameStateMachine implements
		PhaseListener, RenderableGameDriver, InputListener {

	private static final long serialVersionUID = 5347291525993252096L;
	private static final String SAVE_EXTENSION = ".sav";
	private static final int NUM_ROUNDS = 12;

	/**
	 * Loads the saved game using the name of the save file passed in.
	 * 
	 * @param savedGD
	 *            The name of the save file
	 * @return A saved GameDriver.
	 */
	public static NewGameDriver load(File savedGD) throws IOException,
			ClassNotFoundException {

		FileInputStream fis = new FileInputStream(savedGD);
		ObjectInputStream ois = new ObjectInputStream(fis);
		// load stuff here? This method should probably be static
		// or handled by another class
		NewGameDriver gd = (NewGameDriver) ois.readObject();
		ois.close();
		fis.close();
		return gd;
	}// end method

	private Phase currentPhase;
	private Phase intro;
	private Phase ranking;
	private Phase landGrant;
	private Phase development;
	private Phase production;
	private Phase win;

	private Game game;

	/**
	 * Constructs a GameDriver with a default phase (Intro).
	 */
	public NewGameDriver(_ConfigurationPanel panel) {
		this(panel, GameState.INTRO);
	}

	/**
	 * @param startState
	 *            The beginning phase for the game.
	 */
	public NewGameDriver(_ConfigurationPanel panel, GameState startState) {
		
		super(startState);
		
		// Check inputs.
		if (panel == null || startState == null) {
			throw new IllegalArgumentException("Cannot instantiate GameDriver with null inputs.");
		}
		
		game = new Game(panel);

		// Build various phases for game.
		intro = new IntroPhase(game);
		intro.addActionListener(this);

		ranking = new RankingPhase(game);
		ranking.addActionListener(this);

		development = new DevelopmentPhase(game);
		development.addActionListener(this);

		landGrant = new LandGrantPhase(game);
		landGrant.addActionListener(this);
		
		production = new ProductionPhase(game);
		production.addActionListener(this);
	}

	@Override
	public void endPhaseHandler(Phase p) {
		System.out.println("Registered end of state "
				+ p.getCorrespondingState() + " in Game Driver.");

		if (p.getCorrespondingState().equals(GameState.INTRO)) {
			// intro -> ranking
			transition(GameState.PLAYER_RANKING, true);

		} else if (p.getCorrespondingState().equals(GameState.PLAYER_RANKING)) {
			// ranking -> development
			transition(GameState.LAND_GRANT, true);

		} else if (p.getCorrespondingState().equals(GameState.LAND_GRANT)) {
			// land grant -> development
			transition(GameState.DEVELOPMENT, true);

		} else if (p.getCorrespondingState().equals(GameState.DEVELOPMENT)) {
			// development -> ranking
			transition(GameState.PRODUCTION, true);

		} else if (p.getCorrespondingState().equals(GameState.PRODUCTION)) {
			// Production occurs at the end of the round.
			game.nextRound();
			
			// Check for the end of the game.
			if (game.getRound() > NUM_ROUNDS) {
				// Game is over, transition to wining phase.
				// TODO add a winning phase here
				System.out.println("End of game reached.");
				transition(GameState.PLAYER_RANKING,true);
			} else {
				// production -> ranking
				transition(GameState.PLAYER_RANKING, true);
			}
		} else {
			System.out.println("Unknown phase handled in game driver.");
		}
		// TODO need to make sure the game actually ends at some point
	}

	@Override
	public ControllerOwner[] getActiveOwners() {
		if (currentPhase != null) {
			return currentPhase.getActiveOwners();
		} else {
			// Just give input all of the players (this would occur during
			// construction).
			return game.getPlayers();
		}
	}

	@Override
	public RenderablePlayer[] getAllPlayers() {
		return game.getPlayers();
	}

	@Override
	public RenderableMap getCurrentMap() {
		if (currentPhase != null)
			return currentPhase.getCurrentMap();
		else
			return null;
	}

	@Override
	public RenderablePlayer getCurrentPlayer() {
		if (currentPhase != null)
			return currentPhase.getCurrentPlayer();
		else
			return null;
	}

	@Override
	public RenderableTile getCurrentTile() {
		if (currentPhase != null)
			return currentPhase.getCurrentTile();
		return null;
	}

	@Override
	public RenderablePlayer[] getRankedPlayers() {
		if (currentPhase != null)
			return currentPhase.getRankedPlayers();
		else
			return new RenderablePlayer[0];
	}

	@Override
	public int getRemainingTime() {
		if (currentPhase != null)
			return currentPhase.getRemainingTime();
		else
			return 0;
	}

	@Override
	public int getTurnLength() {
		if (currentPhase != null)
			return currentPhase.getTurnLength();
		else
			return 0;
	}

	@Override
	public void inputEventHandler(InputEvent event) {
		if (currentPhase != null)
			currentPhase.inputEventHandler(event);
	}

	/**
	 * Saves the current game by serializing GameDriver
	 * 
	 * @return Whether or not the game successfully saved
	 */
	public boolean save() {
		// Pause the game before saving
		if (currentPhase != null)
			currentPhase.stop();

		// name of the save file
		String saveFile = "";
		// keep asking for a save file
		while (!validFileName(saveFile)) {
			saveFile = JOptionPane
					.showInputDialog(
							null,
							"Enter the desired save file name.\nDo NOT add a file extension.",
							"Save Game", JOptionPane.QUESTION_MESSAGE);
		}// end while
		try {
			FileOutputStream fos = new FileOutputStream(saveFile
					+ SAVE_EXTENSION);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(this);
			oos.flush();
			oos.close();
			fos.close();
			JOptionPane.showMessageDialog(null, ("Game successfully saved to ["
					+ (saveFile + SAVE_EXTENSION) + "]!"), "Save Complete!",
					JOptionPane.INFORMATION_MESSAGE);
		}// end try
		catch (IOException e) {
			JOptionPane.showMessageDialog(null,
					("Uh-oh...\n\n--------------------\n" + e.getMessage()
							+ "\n--------------------\n" + e.getStackTrace()),
					"Save Failed", JOptionPane.ERROR_MESSAGE);
			System.out.println("====================");
			System.out.println("\tSAVE FAILED");
			System.out.println("====================");
			System.out.println(e.getMessage());
			System.out.println("--------------------");
			e.printStackTrace();
			System.out.println("====================");

			// Resume game
			if (currentPhase != null)
				currentPhase.start();
			return false;
		}// end catch

		// Resume game
		if (currentPhase != null)
			currentPhase.start();
		return true;
	}// end method save

	/**
	 * Starts the GameDriver. Should be called only when a game is started or
	 * loaded.
	 */
	public void start() {
		if (currentPhase == null) {
			// Fresh game driver. Transition into constructor-given state.
			transition(state, true);
		} else {
			// Game is has been loaded or start has been called while already
			// running. Do not reset phase.
			transition(state, false);
		}
	}

	/**
	 * Changes the state of the GameDriver and changes the current Phase to the
	 * appropriate class.
	 * 
	 * @param state
	 *            The phase to transition into.
	 * @param reset
	 *            If true, phase will be reset.
	 */
	private void transition(GameState state, boolean reset) {
		// Pause any current phases before transition.
		if (currentPhase != null)
			currentPhase.stop();

		if (state == GameState.INTRO) {
			currentPhase = intro;
		} else if (state == GameState.PLAYER_RANKING) {
			currentPhase = ranking;
		} else if (state == GameState.LAND_GRANT) {
			currentPhase = landGrant;
		} else if (state == GameState.DEVELOPMENT) {
			currentPhase = development;
		} else if (state == GameState.PRODUCTION) {
			currentPhase = production;
		} else {
			// TODO implement more phase changes.
			System.out.println("Unhandled state" + state + " in GameDriver.");
		}

		System.out.println("GameDriver transitioned into " + state + " phase.");
		// Only reset phase if told to do so.
		if (reset)
			currentPhase.reset();
		currentPhase.start();
		changeState(state);
	}

	/**
	 * Determines whether or not a file name is valid (without the extension)
	 * 
	 * @param fileName
	 *            The name of the file (without the extension)
	 * @return Whether or not this is a valid file name
	 */
	public boolean validFileName(String fileName) {
		if (fileName == null) {
			fileName = "save";
		}
		
		// get rid of extra white space
		fileName = fileName.trim();
		// file names cannot be null and cannot be blank
		if (fileName != null && fileName.length() > 0) {
			// file names must be [1, 15] characters
			if (fileName.length() > 15) {
				JOptionPane.showMessageDialog(null,
						"Error: File names cannot be more than 15 characters.",
						"Invalid File Name", JOptionPane.ERROR_MESSAGE);
				return false;
			}// end if
				// file names can only contain alphanumeric characters
			if (fileName.matches("[0-9a-zA-Z ]+")) {
				return true;
			}// end else if
			else {
				JOptionPane
						.showMessageDialog(
								null,
								"Error: File names must only contain alphanumeric characters.",
								"Invalid File Name", JOptionPane.ERROR_MESSAGE);
				return false;
			}// end else
		}// end if
		return false;
	}// end method validFileName
}
